import io
import uuid
import dash
import orjson
from dash import html, dcc
import geopandas as gpd
from shapely.geometry import Polygon
import feffery_antd_components as fac
import feffery_leaflet_components as flc
from dash.dependencies import Input, Output, State, ALL
from feffery_leaflet_components.geometry_utils import Converter

app = dash.Dash(
    __name__,
    suppress_callback_exceptions=True,
    update_title=None
)

app.layout = html.Div(
    [
        # 注入辅助过程记录&数据缓存的存储器
        html.Div(
            [
                # 保存自定义字段信息
                dcc.Store(
                    id='defined-fields-name',
                ),

                # 保存已添加矢量信息
                dcc.Store(
                    id='geometry-record-store'
                ),

                # 保存已添加非矢量信息
                dcc.Store(
                    id='non-geometry-record-store'
                )
            ]
        ),

        flc.LeafletMap(
            [
                flc.LeafletTileLayer(
                    url='http://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}'
                )
            ],
            id='map',
            editToolbar=True,
            editToolbarControlsOptions=dict(
                drawMarker=False,
                drawCircleMarker=False,
                drawPolyline=False,
                drawRectangle=False,
                drawPolygon=True,
                drawCircle=False,
                cutPolygon=True
            ),
            maxDrawnShapes=1,
            center={
                'lng': 121.4,
                'lat': 31.2
            },
            zoom=9,
            style={
                'height': '100%'
            }
        ),

        # 操作区域容器
        html.Div(
            fac.AntdResult(
                icon=fac.AntdIcon(
                    icon='fc-globe',
                    style={
                        'fontSize': '90px'
                    }
                ),
                status='info',
                title='在线GeoJSON数据生成器',
                subTitle=fac.AntdButton(
                    '创建新的文件',
                    id={
                        'type': 'button',
                        'index': 'create-file'
                    },
                    size='large',
                    type='primary'
                )
            ),
            id='operation-area',
            style={
                'width': '450px',
                'height': '300px',
                'background': 'white',
                'borderRadius': '8px',
                'boxShadow': '0 0 4px 2px hsl(0deg 0% 69% / 50%)',
                'padding': '10px',
                'position': 'absolute',
                'top': '50%',
                'right': '50%',
                'transform': 'translate(50%, -75%)',
                'zIndex': 999,
                'display': 'flex',
                'justifyContent': 'center',
                'alignItems': 'center',
                'transition': 'all 0.5s ease-in-out'
            }
        )
    ],
    style={
        'width': '100vw',
        'height': '100vh',
        'position': 'relative'
    }
)


@app.callback(
    [Output('operation-area', 'children'),
     Output('operation-area', 'style')],
    Input(
        {
            'type': 'button',
            'index': ALL
        },
        'nClicks'
    ),
    State('operation-area', 'style'),
    prevent_initial_call=True
)
def update_operation_area(_, old_style):

    if dash.ctx.triggered_id.get('index') == 'create-file':
        del old_style['transform']
        return [
            fac.AntdRow(
                [
                    fac.AntdCol(
                        fac.AntdSteps(
                            id='operation-steps',
                            steps=[
                                {
                                    'title': '定义字段'
                                },
                                {
                                    'title': '添加数据'
                                },
                                {
                                    'title': '导出数据'
                                }
                            ],
                            progressDot=True,
                            direction='vertical'
                        ),
                        flex='none',
                        style={
                            'height': '100%'
                        }
                    ),
                    fac.AntdCol(
                        [
                            html.Div(
                                id='current-step-container',
                                style={
                                    'height': '100%',
                                    'borderRadius': '8px',
                                    'background': 'white',
                                    'overflowY': 'auto'
                                }
                            ),

                            fac.AntdButton(
                                fac.AntdIcon(
                                    icon='antd-arrow-right'
                                ),
                                id='go-next-step',
                                type='primary',
                                shape='circle',
                                style={
                                    'position': 'absolute',
                                    'bottom': '25px',
                                    'right': '25px'
                                }
                            )
                        ],
                        flex='auto',
                        style={
                            'height': '100%',
                            'padding': '15px',
                            'background': 'rgb(242, 243, 248)',
                            'position': 'relative'
                        }
                    )
                ],
                style={
                    'height': '100%',
                    'width': '100%'
                }
            ),
            {
                **old_style,
                'top': '20px',
                'right': '20px',
                'width': '700px',
                'height': '500px'
            }
        ]

    return dash.no_update


@app.callback(
    Output('operation-steps', 'current'),
    Input('go-next-step', 'nClicks'),
    State('operation-steps', 'current')
)
def go_next_step(nClicks, current):

    if nClicks:
        return current + 1

    return 0


@app.callback(
    Output('current-step-container', 'children'),
    Input('operation-steps', 'current'),
    [State('defined-fields-name', 'data')]
)
def step_to_content(current, defined_fields_name):
    if isinstance(current, int):
        if current == 0:
            return html.Div(
                [
                    fac.AntdSpace(
                        [
                            fac.AntdInput(
                                defaultValue='id',
                                disabled=True,
                                style={
                                    'width': '250px'
                                }
                            ),
                            fac.AntdButton(
                                '+',
                                id='add-field-button',
                                type='primary',
                                shape='circle'
                            ),
                        ],
                        style={
                            'justifyContent': 'center'
                        }
                    )
                ],
                id='define-field-container',
                style={
                    'width': '70%',
                    'margin': '0 auto',
                    'paddingTop': '25px',
                    'display': 'flex',
                    'flexDirection': 'column',
                    'justifyContent': 'center',
                    'gap': '5px',
                    'overflowX': 'hidden'
                }
            )

        elif current == 1:

            return html.Div(
                [
                    fac.AntdButton(
                        '添加当前矢量',
                        id='add-current-drawn-shape',
                        type='primary',
                        disabled=True,
                        style={
                            'marginBottom': '5px'
                        }
                    ),
                    fac.AntdTable(
                        id='geometry-record-table',
                        columns=[
                            {
                                'title': field_name,
                                'dataIndex': field_name,
                                'width': 100,
                                'editable': True
                            }
                            for field_name in defined_fields_name
                        ],
                        bordered=True
                    )
                ],
                style={
                    'padding': '10px'
                }
            )

        elif current == 2:

            return html.Div(
                [
                    fac.AntdButton(
                        [
                            fac.AntdIcon(
                                icon='antd-download',
                                style={
                                    'paddingRight': '5px'
                                }
                            ),
                            '导出为GeoJSON文件'
                        ],
                        id='export-to-geojson',
                        type='primary'
                    ),
                    dcc.Download(
                        id='exported-geojson'
                    )
                ],
                style={
                    'height': '100%',
                    'display': 'flex',
                    'alignItems': 'center',
                    'justifyContent': 'center'
                }
            )


@app.callback(
    Output('define-field-container', 'children'),
    Input('add-field-button', 'nClicks'),
    Input(
        {
            'type': 'delete-field-button',
            'index': ALL
        },
        'nClicks'
    ),
    State('define-field-container', 'children')
)
def define_field_control(nClicksAdd, nClicksDelete, old_children):

    if dash.ctx.triggered_id:

        if dash.ctx.triggered_id == 'add-field-button':
            new_uuid = str(uuid.uuid4())
            return [
                *old_children,
                fac.AntdSpace(
                    [
                        fac.AntdInput(
                            id={
                                'type': 'field-name',
                                'index': new_uuid
                            },
                            placeholder='请输入新字段名称',
                            style={
                                'width': '250px'
                            }
                        ),
                        fac.AntdButton(
                            '-',
                            id={
                                'type': 'delete-field-button',
                                'index': new_uuid
                            },
                            type='primary',
                            shape='circle',
                            danger=True
                        ),
                    ],
                    style={
                        'justifyContent': 'center'
                    }
                )
            ]

        elif dash.ctx.triggered_id.get('type') == 'delete-field-button':

            # 剔除删除按钮对应的字段输入框
            return [
                old_children[0],
                *[
                    child for child
                    in old_children[1:]
                    if child['props']['children'][0]['props']['id']['index'] != dash.ctx.triggered_id.get('index')
                ]
            ]

    return dash.no_update


@app.callback(
    Output('defined-fields-name', 'data'),
    Input(
        {
            'type': 'field-name',
            'index': ALL
        },
        'value'
    )
)
def record_defined_fields_name(value_list):

    return ['id', *[x for x in value_list if x]]


@app.callback(
    Output('add-current-drawn-shape', 'disabled'),
    Input('map', '_drawnShapes')
)
def should_add_drawn_shape_button_disabled(_drawnShapes):

    if _drawnShapes:
        return False

    return True


@app.callback(
    [Output('geometry-record-table', 'data'),
     Output('geometry-record-store', 'data')],
    Input('add-current-drawn-shape', 'nClicks'),
    [State('map', '_drawnShapes'),
     State('geometry-record-table', 'data'),
     State('defined-fields-name', 'data'),
     State('geometry-record-store', 'data')]
)
def update_geometry_record_table(nClicks,
                                 _drawnShapes,
                                 old_data,
                                 defined_fields_name,
                                 geometry_record_store):

    if nClicks:
        return [
            [
                *(old_data or []),
                {
                    **{
                        field_name: None
                        for field_name in defined_fields_name
                    },
                    'id': _drawnShapes[0]['id']
                }
            ],
            {
                **(geometry_record_store or {}),
                # 基于flc自带的数据转换工具类将当前绘制的矢量转换到wgs84坐标系
                # 下的数组格式数据
                _drawnShapes[0]['id']: Converter.convert_drawn_shape(
                    _drawnShapes[0],
                    source_crs='gcj',
                    target_crs='wgs'
                )
            }
        ]

    return dash.no_update


@app.callback(
    Output('non-geometry-record-store', 'data'),
    Input('geometry-record-table', 'data')
)
def sync_non_geometry_record_store(data):

    return data


@app.callback(
    Output('exported-geojson', 'data'),
    Input('export-to-geojson', 'nClicks'),
    [State('geometry-record-store', 'data'),
     State('non-geometry-record-store', 'data')],
    prevent_initial_call=True
)
def export_to_geojson(nClicks, geometry_record_store, non_geometry_record_store):

    if geometry_record_store:

        for i, record in enumerate(non_geometry_record_store):

            current_shape = geometry_record_store[str(record['id'])]

            if current_shape == 1:
                record['geometry'] = Polygon(current_shape[0])

            else:
                record['geometry'] = Polygon(
                    current_shape[0],
                    current_shape[1:]
                )

        gdf = gpd.GeoDataFrame(
            non_geometry_record_store,
            crs='EPSG:4326'
        )

        return dict(content=gdf.to_json(), filename="导出结果.geojson")


if __name__ == '__main__':
    app.run(debug=True)
